package jaux.tank.api.controller;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import javax.annotation.PostConstruct;
import javax.persistence.EntityManager;
import javax.persistence.metamodel.EntityType;
import javax.persistence.metamodel.SingularAttribute;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.ConvertUtils;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import jaux.tank.bean.constant.factory.PageFactory;
import jaux.tank.bean.dto.PageRequestModel;
import jaux.tank.bean.vo.EntityQueryCondition;
import jaux.tank.bean.vo.front.Ret;
import jaux.tank.bean.vo.front.Rets;
import jaux.tank.dao.GeneralRepository;
import jaux.tank.utils.PageUtils;
import jaux.tank.utils.factory.Page;

@RestController
@RequestMapping("/api/entities/")
@Api(tags = "实体操作web接口")
public class EntitiesController {
	
	private static final Logger logger  = LoggerFactory.getLogger(EntitiesController.class);
	@Autowired
	EntityManager entityManager;
	
	private Set<EntityType<?>> entityTypes;
	
	private ObjectMapper om;
	
	@Autowired
	GeneralRepository repo;
	
	@PostConstruct
	public void init() {
		this.entityTypes = entityManager.getMetamodel().getEntities();
		this.om = new ObjectMapper();
	}
	@GetMapping("{entityName}/")
	@ApiOperation("分页查询实体对象")
	public Ret<Page> queryEntity(@PathVariable String entityName,
			@ModelAttribute(binding = false) PageRequestModel pageRequest){
		Optional<EntityType<?>> entityOpt = entityTypes.stream().filter(et->et.getName().equals(entityName)).findAny();
		if(!entityOpt.isPresent())
			return Rets.failure("entity "+entityName+" dose not exists");
		Page<Object> page = new PageFactory<>().defaultPage();
		org.springframework.data.domain.Page opage = repo.findAll(entityOpt.get(),PageUtils.convertPageToPageable(page));
		page.setRecords(opage.getContent());
		page.setTotal((int)opage.getTotalElements());
		return Rets.success(page);
	}
	@PostMapping("{entityName}/")
	@Modifying
	@ApiOperation("添加新的实体对象")
	public Ret<Object> postEntity(@PathVariable String entityName,
			@RequestBody String entityJson){
		Optional<EntityType<?>> entityOpt = entityTypes.stream().filter(et->et.getName().equals(entityName)).findAny();
		if(!entityOpt.isPresent())
			return Rets.failure("entity "+entityName+" dose not exists");
		EntityType<?> entityType = entityOpt.get();
		try {
			return Rets.success(repo.save(entityType,om.readValue(entityJson, entityType.getJavaType())));
		} catch (IOException e) {
			logger.error("entity json parse failed",e);
			return Rets.failure("entity json parse failed");
		}
	}
	
	@GetMapping("{entityName}/{entityId}/")
	@ApiOperation("查询单一实体对象")
	public Ret<Object> queryEntity(@PathVariable String entityName,
			@PathVariable String entityId){
		Optional<EntityType<?>> entityOpt = entityTypes.stream().filter(et->et.getName().equals(entityName)).findAny();
		if(!entityOpt.isPresent())
			return Rets.failure("entity "+entityName+" dose not exists");
		EntityType<?> entityType = entityOpt.get();
		Class<?> idClzz = entityType.getIdType().getJavaType();
		Object realEntityid = ConvertUtils.convert(entityId, idClzz);
		Optional<?> objOpt = repo.findById(entityType, realEntityid);
		if(!objOpt.isPresent())
			return Rets.failure("object dose not exists");
		return Rets.success(objOpt.get());
	}
	
	@PutMapping("{entityName}/{entityId}/")
	@ApiOperation(value = "更新单一实体对象" )
	public Ret<Object> updateEntity(@PathVariable String entityName,
			@PathVariable String entityId,
			@ApiParam("指定需要更新的实体属性，未指定则整体更新") @RequestHeader(required = false, name = "Update-Attributes") String[] updateAttributes, 
			@RequestBody String entityJson){
		Optional<EntityType<?>> entityOpt = entityTypes.stream().filter(et->et.getName().equals(entityName)).findAny();
		if(!entityOpt.isPresent())
			return Rets.failure("entity "+entityName+" dose not exists");
		EntityType<?> entityType = entityOpt.get();
		Object entityInst = null;
		try {
			entityInst = om.readValue(entityJson, entityType.getJavaType());
		} catch (IOException e) {
			logger.error("entity json parse failed",e);
			return Rets.failure("entity json parse failed");
		}
		Class<?> idClzz = entityType.getIdType().getJavaType();
		SingularAttribute<?, ?> idAttr = entityType.getId(idClzz);
		Object realEntityid = ConvertUtils.convert(entityId, idClzz);
		try {
			BeanUtils.setProperty(entityInst, idAttr.getName(), realEntityid);
		} catch (IllegalAccessException | InvocationTargetException e) {
			logger.error("entity id setting failed",e);
			return Rets.failure("entity id setting failed");
		}
		Set<String> updateAttributeNames = null;
		if(!StringUtils.isAllBlank(updateAttributes)) {
			
			  updateAttributeNames = Arrays.stream(updateAttributes).filter(ua->ua!=null && ua.length()>0 && !StringUtils.isAllBlank(ua))
			.flatMap(ua->Arrays.stream(ua.split(",")))
			.filter(ua->StringUtils.isNotBlank(ua))
			.map(ua->ua.trim())
			.collect(Collectors.toSet());
		}
		if(CollectionUtils.isNotEmpty(updateAttributeNames)) {
			Optional<?> oldInst = repo.findById(entityType, realEntityid);
			if(oldInst.isPresent()) {
				Object oldEntityInst = oldInst.get();
				for(String attrName:updateAttributeNames) {
					try {
						BeanUtils.setProperty(oldEntityInst, attrName, PropertyUtils.getProperty(entityInst, attrName));
					} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
						logger.error("entity attribute setting failed", e);
					}
				}
				return Rets.success(repo.save(entityType, oldEntityInst));
				
			}
		}else {
			return Rets.success(repo.save(entityType, entityInst));
			
		}
		return Rets.failure("entity update failed");
	}
	@DeleteMapping("{entityName}/{entityId}/")
	@ApiOperation("删除实体对象")
	public Ret<Object> deleteEntity(@PathVariable String entityName,
			@PathVariable String entityId){
		Optional<EntityType<?>> entityOpt = entityTypes.stream().filter(et->et.getName().equals(entityName)).findAny();
		if(!entityOpt.isPresent())
			return Rets.failure("entity "+entityName+" dose not exists");
		EntityType<?> entityType = entityOpt.get();
		Class<?> idClzz = entityType.getIdType().getJavaType();
		Object realEntityid = ConvertUtils.convert(entityId, idClzz);
		repo.deleteById(entityType, realEntityid);
		return Rets.success(realEntityid);
	}
	
	@GetMapping("{entityName}/search/")
	@ApiOperation(value = "根据条件查询实体对象",notes = "查询条件数组中的条件之间为并（and）的关系，一个查询条件与它属性conditions中的查询条件为或（or）的关系.<br>"
			+ "查询条件对象包含以下属性：<br>"
			+ "<ul>"
			+ "<li>property:查询属性目标，可用.号分割进行层次查询；</li>"
			+ "<li>queryType：查询类型，默认为EQUAL,可选值：EQUAL,LIKE,LESS,LESS_OR_EQUAL,GREATER,GREATER_OR_EQUAL,IN,NOT_IN,IS_NULL,IS_NOT_NULL,IS_EMPTY,IS_NOT_EMPTY,CONTAINS,OR,AND;</li>"
			+ "<li>value:查询值;</li>"
			+ "<li>conditions:与父查询条件默认为或(or)关系的查询条件数组，可通过queryType属性设置为并(and)关系;</li>"
			+ "</ul>"
			+ "<br>例如：{\"property\":\"parent.name\",\"queryType\":\"EQUAL\",\"value\":\"1\"}<br>"
			+ "<br>例如：{\"property\":\"parent.name\",\"queryType\":\"EQUAL\",\"value\":\"1\",\"conditions\":[{\"property\":\"id\",\"value\":\"fa9b88b5-a8a2-4ba7-aded-14a59c2d1c67\",\"queryType\":\"EQUAL\"}]}<br>"
			+ "<br>例如：{\"queryType\":\"AND\",\"conditions\":[{\"property\":\"id\",\"value\":\"fa9b88b5-a8a2-4ba7-aded-14a59c2d1c67\",\"queryType\":\"EQUAL\"},"
			+ "{\"property\":\"name\",\"value\":\"lisi\",\"queryType\":\"EQUAL\"}]}<br>"
			)
	public Ret<Page> search(@PathVariable String entityName,
			@RequestParam(required = false) @ApiParam("json数组格式的查询条件") String queryParamsJson,
			@ModelAttribute(binding = false) PageRequestModel pageRequest){
		EntityType<?> entityType = getEntityType(entityName);
		if(entityType == null)
			return Rets.failure("entity "+entityName+" dose not exists");
		List<EntityQueryCondition> queryConditionLst  =null;
		if(StringUtils.isNotBlank(queryParamsJson)) {
		try {
			 queryConditionLst = om.readValue(queryParamsJson, new TypeReference<List<EntityQueryCondition>>() {});
		} catch (IOException e) {
			logger.error("query condition json parse failed",e);
			return Rets.failure("query condition json parse failed");
		}
		}
		
		Page<Object> page = new PageFactory<>().defaultPage();
		org.springframework.data.domain.Page opage = repo.findAll(entityType,queryConditionLst,PageUtils.convertPageToPageable(page));
		page.setRecords(opage.getContent());
		page.setTotal((int)opage.getTotalElements());
		return Rets.success(page);
	}
	
	private EntityType<?> getEntityType(String entityName){
		Optional<EntityType<?>> entityOpt = entityTypes.stream().filter(et->et.getName().equals(entityName)).findAny();
		return entityOpt.orElse(null);
	}
}
